package com.mini.game.dbbase;

import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.mini.common.RedisService;
import com.mini.game.ApplicationContextProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.jpa.repository.JpaRepository;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

public abstract class AbstractCacheRepository<T extends IEntity, ID extends Serializable> {
    private final static Logger logger = LoggerFactory.getLogger(AbstractCacheRepository.class);
    private final static int CACHE_TIME = 10;
    //    private final static int REDIS_CACHE_TIME = 10 * 60;
//    private final static String DATA = "data_";
    private int delaySecond;

    private LoadingCache<ID, CacheEntity> entityCache;

    private DataCenter dataCenter;

    private Class entityCls;

    private String clsName;

    private RedisService redisService;

    public AbstractCacheRepository() {
        this(DelaySaveType.SIXTY.getSecond());
    }

    public AbstractCacheRepository(int delaySecond) {
        this.delaySecond = delaySecond;
        Type type = getClass().getGenericSuperclass();
        Type[] parameter = ((ParameterizedType) type).getActualTypeArguments();
        entityCls = (Class<T>) parameter[0];
        clsName = entityCls.getSimpleName();
        CacheLoader<ID, CacheEntity> cacheLoader = id -> {
//            logger.info("cacheLoader userId={}",id);
            Optional<T> optional = getJpaRepository().findById(id);
            T t = null;
            if (optional.isPresent()) {
                t = optional.get();
//                logger.info("cacheLoader userId={} from db",id);
            }
//            logger.info("cacheLoader userId={} from db create cache",id);
            return new CacheEntity(t);
        };
        entityCache = Caffeine.newBuilder().expireAfterAccess(CACHE_TIME, TimeUnit.MINUTES).build(cacheLoader);
        redisService = ApplicationContextProvider.getBean(RedisService.class);
    }


    public String getIdKey(Serializable id) {
        String key = null;
        if (!(id instanceof String)) {
            key = id.toString();
        } else {
            key = (String) id;
        }
//        return clsName + "_" + id;
        return key;
    }

    public T cacheLoad(ID id) {
//        String key = getIdKey(id);
        CacheEntity<T> cacheEntity = entityCache.get(id);
        if (cacheEntity != null) {
            return cacheEntity.getT();
        }
//        String str = getRedisUtil().getString(DATA + key);
//        if (str != null) {
//            return (T) JacksonUtil.stringToBean(str, entityCls);
//        } else {
//            Optional<T> optional = getJpaRepository().findById(id);
//            if (optional.isPresent()) {
//                T t = optional.get();
//                getRedisUtil().set(DATA + key, JacksonUtil.toJsonString(t), REDIS_CACHE_TIME);
//                return t;
//            }
//        }
        return null;
    }

    public void cacheUpdate(T t) {
        ID id = (ID) t.primaryKeyValue();
//        String key = getIdKey(id);
//        getRedisUtil().set(DATA + key, JacksonUtil.toJsonString(t), REDIS_CACHE_TIME);
        //数据库定时保存
        CacheEntity<T> cacheEntity = entityCache.get(id);
        if (cacheEntity != null && cacheEntity.getT() == null) {
            logger.error("cacheUpdate clsName={} id={} is null", t.getClass().getSimpleName(), id);
        }
        cacheEntity.setT(t);
        entityCache.put(id, cacheEntity);
        getDataCenter().cacheUpdate(t, delaySecond, getJpaRepository());
    }

    public void cacheInsert(T t) {
        ID id = (ID) t.primaryKeyValue();
//        String key = getIdKey(id);
//        getRedisUtil().set(DATA + key, JacksonUtil.toJsonString(t), REDIS_CACHE_TIME);
        CacheEntity<T> cacheEntity = entityCache.get(id);
        if (cacheEntity != null && cacheEntity.getT() != null) {
            logger.error("cacheInsert clsName={} id={} is not null", t.getClass().getSimpleName(), id);
        }
        cacheEntity.setT(t);
        entityCache.put(id, cacheEntity);
//        getRedisUtil().set(DATA + key, JacksonUtil.toJsonString(t), TIME);
        getDataCenter().cacheInsert(t, delaySecond, getJpaRepository());
    }

    public void cacheDelete(T t) {
        ID id = (ID) t.primaryKeyValue();
//        String key = getIdKey(id);
//        getRedisUtil().del(DATA + key);
        CacheEntity<T> cacheEntity = entityCache.get(id);
        cacheEntity.setT(null);
        getDataCenter().cacheDelete(t, delaySecond, getJpaRepository());
    }

    public abstract JpaRepository<T, ID> getJpaRepository();

    public DataCenter getDataCenter() {
        if (dataCenter == null) {
            dataCenter = ApplicationContextProvider.getBean(DataCenter.class);
        }
        return dataCenter;
    }
}
